# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

__revision__ = '$Hg: user.py,v 979d136275fb 2007/01/14 18:37:34 +0100 $'

# system imports
import fnmatch
import re
import time
import types

# DDB imports
from ddb.log import log
from ddb.log import log
from ddb.config import configFile
from ddb.settings import *
from ddb.cache import cache, cached

# twisted imports
from twisted.internet import reactor

_re_ip = re.compile('^(?:\d{1,3}\.){3}\d{1,3}$')

try:
    set()
except:
    from sets import Set as set

def cleanIdent(ident):
    # strip all prefix chars
    for prefix in IDENT_PREFIX:
        if ident.startswith(prefix):
            ident = ident[len(prefix):]
            return ident
    return ident    

def cleanMask(mask):
    # strip all prefix chars
    for prefix in IDENT_PREFIX:
        if '!%s' % prefix in mask:
            mask = mask.replace('!%s' % prefix, '!')
            return mask
    return mask   

def parseMask(mask):
    nick  = mask
    ident = ''
    host  = ''
    if '@' in nick:
        nickIdent, host = nick.split('@', 1)
        if '!' in nickIdent:
            nick, ident = nickIdent.split('!', 1)
        else:
            ident = nickIdent
            nick  = '*'
    return [nick, ident, host]

def makeMask(mask):
    nick, ident, host = parseMask(mask)
    parts = host.split('.')
    if len(parts) > HOST_DOTS_MASK:
        if _re_ip.match(host):
            parts[3] = '*'
        else:
            parts[0] = '*'
    ident = cleanIdent(ident)
    nick  = '*'
    return '%s!%s@%s' % (nick, ident, '.'.join(parts))
    
class IUserConfig(configFile):
    def __init__(self):
        configFile.__init__(self, 'data/userconfig.ini', format='ini')
        self.load(True)

    def get(self, username):
        return IUserConfigSection(self, username)

class IUserRegister:
    """Manage the user records."""
    users = []

    def __init__(self):
        """ Load user configuration file """
        self.cache  = cache()
        self.config = None
        self.load()

    def add(self, username, mask):
        """Add a user."""
        self.config.set(username, 'mask', [mask])

    def get(self, username):
        return self.config.sectionGet(username)

    def remove(self, username):
        self.config.sectionRemove(username)

    def load(self):
        self.config = configFile('config/users.ini', 'ini', True)
        self.config.load()

    def save(self):
        self.config.save()

    def names(self):
        return self.config.sections()

    def match(self, mask):
        mask  = cleanMask(mask)
        cache = self.cache.lookup(mask)
        if cache:
            return cache['user']
        elif cache == None:  # None=negative cache hit, False=lookup failed
            return None
        for userName in self.names():
            if not self.config.optionExists(userName, 'mask'):
                continue
            for userMask in self.config.get(userName, 'mask'):
                if not '!' in userMask:
                    userMask = '*!%s' % userMask
                #log.msg('compairing mask for %s: %s =? %s' % (
                #    userName, mask, userMask), debug=True)
                if fnmatch.fnmatch(mask, userMask):
                    self.cache.add(mask, user=userName)
                    log.msg('Matched user %s: %s' % (mask, userName))
                    return userName
        return None

    def rights(self, username, target='*'):
        flags = []
        if not self.config.optionExists(username, 'flag'):
            return ''
        else:
            for flag in self.config.get(username, 'flag'):
                if flag and ',' in flag:
                    ftarget, fflags = flag.split(',', 1)
                    if ftarget.lower() == target.lower() or ftarget == '*':
                        flags.append(fflags)
        # filter duplicates
        flags = list(set(flags))
        flags.sort()
        return ''.join(flags)

    def rightsAdd(self, username, target, flags):
        if not self.config.get(username, 'flag'):
            self.config.set(username, 'flag', ['%s,%s' % (target, flags)])
        else:
            oldFlags = {}
            for flag in self.config.get(username, 'flag'):
                if flag and ',' in flag:
                    ftarget, fflags = flag.split(',', 1)
                    oldFlags[ftarget] = [x for x in fflags]
            if oldFlags.has_key(target):
                oldFlags[target] = oldFlags[target]+[x for x in flags if x not in oldFlags[target]]
            else:
                oldFlags[target] = flags
            newFlags = []
            for ftarget, fflags in oldFlags.items():
                fflags = list(set(fflags)) # unique + sort, but than the quick way :-)
                newFlags.append('%s,%s' % (ftarget, ''.join(fflags)))
            self.config.set(username, 'flag', newFlags)

    def keyCheck(self, username, key):
        return (key in self.config.get(username, 'ssh-key'))

    def keyGet(self, username):
        keys = self.config.get(username, 'ssh-key')
        if not keys:
            return []
        return keys

    def passwordCheck(self, username, password):
        if not username in self.keys():
            return False
        if not self[username].has_key('password'):
            return False
        return (password in self[username]['password']) 

    def userCheck(self, username):
        return self.config.sectionExists(username)

userRegister = IUserRegister()

class user:
    """Class with all information on an user."""

    username = None # needed for cached()
    flags    = None # needed for cached()
    recache  = True

    def __init__(self, identity, mask, seen=0):
        print 'ADD USER', identity, mask
        self.identity = identity
        # no nick in mask
        #if '!' in mask:
        #    mask = mask.split('!')[1]
        self.mask     = mask
        self.username = self._recacheUserName()
        self.flags    = self._recacheUserFlags()
        if seen:
            self.seen = seen
        else:
            self.seen = time.time()

    def _disableCache(self):
        self.recache = False

    def _recacheUserName(self):
        if not self.recache and self.username:
            return
        self.username = userRegister.match(self.mask)
        reactor.callLater(CACHE_INVALIDATE, self._recacheUserName)
        return self.username

    def _recacheUserFlags(self):
        self.flags = self.rights()
        reactor.callLater(CACHE_INVALIDATE, self._recacheUserFlags)
        return self.flags

    def rights(self, target='*'):
        return userRegister.rights(self.username, target)

    def __repr__(self):
        return '<ddb.user.user identity=%s, mask=%s, username=%s>' % (self.identity, self.mask, self.username)

class userManager:
    def __init__(self, idx, *ownIdentity):
        self.idx        = idx
        self.users      = {
            '<SELF>': user(*ownIdentity)
            }    # to keep track what users are on the transport

    def add(self, identity, mask):
        return self.update(identity, mask)

    def update(self, identity, mask):
        identity = identity.lower()
        # create new record if it is a new user, otherwise use cache
        if not identity in self.users.keys():
            newUser = user(identity, mask)
            self.users[identity] = newUser
        return self.users[identity]

    def remove(self, identity):
        # remove completely, no conference specified
        if not identity in self.users.keys():
            return None
        oldUser = self.users[identity]
        del self.users[identity]
        return oldUser

    def flush(self):
        records = self.users.keys()
        for record in records:
            del self.users[record]

    def findByIdentity(self, identity):
        for thisUser in self.users.keys():
            if self.users[thisUser].identity == identity:
                return self.users[thisUser]
        return None

# vim:ts=4:sw=4:et:
